package performanceProfiling;

import java.lang.management.*;
import java.util.ArrayList;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.io.*;

/* To make use of this class you must first create a 
 * PerfomanceProfiling object in your code. For the game engine,
 * it makes since to do this in the Board() constructor
 * 
 * wrap the vial functions that you would like to profile with PP method calls.
 * For example, wrap the call to "think" with thinkTimeStart() and thinkTimeStop.
 * You get the idea.
 * 
 *  To include on screen reporting, call the function showAll() after
 *  initializing the object.
 *  You can also choose to display on specific metrics with the showBlah methods.
 *  Then you must call printToScreen() somewhere during the paint method.
 * 
 * class will generate an file called "perfomance_log.txt" 
 * to print to this file, the printToFile() method must be invoked at
 * the end of the animation cycle.
 * 
 * The timing is done using the system clock which is not very reliable, 
 * but it gives a good idea of user experience. The system clock will be affected by processes. 
 * 
 */
public class PerformanceProfiling {
	
	private int spriteTreeSize = 0;
	private int drawTreeSize = 0;
	private int numCollisionsFound = 0;
	private int numDoesCollideAttempts = 0;
	private int numAnimationCycles = 0;
	private int numRepaint = 0;
	
	private int numCyclesFpsCompute = 10;
	
	
	private long animation_startTimeNano;
	private long animation_taskTimeNano;
	private long fps_startTimeNano;
	private long fps_taskTimeNano;
	private long think_startTimeNano;
	private long draw_startTimeNano;
	private long think_taskTimeNano;
	private long draw_taskTimeNano;
	
	private boolean showFPS = false;
	private boolean showSpritesTreeSize = false;
	private boolean showDrawTreeSize = false;
	private boolean showNumCollisionsFound = false;
	private boolean showNumDoesCollideAttempts = false;
	private boolean showNumAnimationCycles = false;
	private boolean fps_compute = false;
	
	private int fpsAmimationCycleCounter = 0;
	
	
	ArrayList<AnimationCycleUnit> log;	
	ArrayList<ProfileUnit> think;
	ArrayList<ProfileUnit> draw;
	ArrayList<ProfileUnit> animation;
	
	FileWriter fstream;
	BufferedWriter out;
	
	public PerformanceProfiling(){
		
		log = new ArrayList<AnimationCycleUnit>();
		think = new ArrayList<ProfileUnit>();
		draw = new ArrayList<ProfileUnit>();
		animation = new ArrayList<ProfileUnit>();
		
		try {
			fstream = new FileWriter("performance_log.txt");
			out = new BufferedWriter(fstream);
		}catch(Exception e){
			
		}
	}
		
	
	public void showAll(){
		this.showFPS = true;
		this.showSpritesTreeSize = true;
		this.showDrawTreeSize = true;
		this.showDrawTreeSize = true;
		this.showNumCollisionsFound = true;
		this.showNumDoesCollideAttempts = true;
		this.showNumAnimationCycles = true;
	}

	public void showFPS(){
		this.showFPS = true;		
	}
	public void showSpritesTreeSize(){
		this.showSpritesTreeSize = true;		
	}
	public void showDrawTreeSize(){
		this.showDrawTreeSize = true;		
	}
	public void showNumCollisionsFound(){
		this.showNumCollisionsFound = true;		
	}
	public void showNumDoesCollideAttempts(){
		this.showNumDoesCollideAttempts = true;		
	}
	
	public void fpsStart(){
		if(fps_compute == false){
			fpsAmimationCycleCounter = this.numAnimationCycles;
			this.startTiming(4);
			fps_compute = true;
		}
	}
	public void fpsStop(){
		if((numAnimationCycles >= fpsAmimationCycleCounter + numCyclesFpsCompute) && (fps_compute == true)){
			this.stopTiming(4);
			fps_compute = false;
		}
		
	}	
	public void cycleTimeStart(){
		this.startTiming(3);		
	}
	public void cycleTimeStop(){
		this.stopTiming(3);		
	}	
	
	public void thinkTimeStart(){
		this.startTiming(1);		
	}
	public void thinkTimeStop(){
		this.stopTiming(1);		
	}
	public void drawTimeStart(){
		this.startTiming(2);		
	}
	public void drawTimeStop(){
		this.stopTiming(2);		
	}
	public void incrementDoesCollideAttempts(){
		this.numDoesCollideAttempts++;	
	}
	public void incrementCollisionsFound(){
		this.numCollisionsFound++;	
	}
	public void incrementSpriteTreeSize(){
		this.spriteTreeSize++;	
	}
	public void incrementDrawTreeSize(){
		this.drawTreeSize++;	
	}
	
	public void startTiming(int s){		
		switch(s){
			case 1: think_startTimeNano = System.nanoTime( ); break;
			case 2: draw_startTimeNano = System.nanoTime( ); break;
			case 3: animation_startTimeNano = System.nanoTime( ); break;
			case 4: fps_startTimeNano = System.nanoTime( ); break;
			default: ;
		}	
	}		
	
	public void stopTiming(int s){
		switch(s){
			case 1: think_taskTimeNano = System.nanoTime( ) - think_startTimeNano; 
					think.add(new ProfileUnit(think_taskTimeNano ));
			break;
			case 2: draw_taskTimeNano = System.nanoTime( ) - draw_startTimeNano; 
					draw.add(new ProfileUnit(draw_taskTimeNano ));
			break;
			case 3: animation_taskTimeNano = System.nanoTime( ) - animation_startTimeNano; 
					animation.add(new ProfileUnit( animation_taskTimeNano));
			break;
			case 4: fps_taskTimeNano = System.nanoTime( ) - fps_startTimeNano; 
					
			break;
			default: ;
		}		
	}
	
	public double convertNanoToSeconds(long nano){
		//System.out.println(nano);
		
		//System.out.println((double)(nano * .000000001));
		return (double)(nano * .000000001);
		
	}
	
	public double getFPS(){
		
		return (double)((double)(numCyclesFpsCompute) / (double)(convertNanoToSeconds(fps_taskTimeNano)));		
	}
	
	public void printToFile(){
		
		try {
			fstream = new FileWriter("performance_log.txt", true);
			out = new BufferedWriter(fstream);
			if(log.size()>0){
				AnimationCycleUnit a = log.get(log.size()-1);
				out.write("\n");
				out.write("*********************************\n");
				out.write("animationCycleId  = " + a.animationCycleId + "\n");
				out.write("spriteTreeSize  = " + a.spriteTreeSize + "\n");
				out.write("drawTreeSize  = " +  a.drawTreeSize + "\n");
				out.write("numDoesCollidePerformed  = " +  a.numDoesCollidePerformed + "\n");
				out.write("numCollisionsFound  = " +  a.numCollisionsFound + "\n");
				out.write("fPS  = " +  a.fPS + "\n");
				out.write("cycleDrawTime = " +  a.cycleDrawTime + "\n");
				out.write("cycleThinkTime = " +  a.cycleThinkTime + "\n");
				out.write("\n");
				out.close();
				} 
		}catch (IOException e) {
			
			e.printStackTrace();
		}		
	}
	
	public void printToScreen(Graphics2D g, int x, int y){
		int y_offset = 1;
		int y_spacing = 15;
		if((showFPS == true) || 
		   (showSpritesTreeSize == true)||
		   (showDrawTreeSize == true) ||
		   (showNumCollisionsFound == true)||
		   (showNumDoesCollideAttempts == true)||
		   (showNumAnimationCycles == true)
		   ){
			g.setColor(new Color(255, 255, 255, 150));
			g.fillRect(x, y, 250, 150);
			
			g.setColor(Color.red);
			Font small = new Font("Helvetica", Font.BOLD, 10);
			g.setFont(small);
			if(showFPS == true){				
				g.drawString("Frames Per Second: " + getFPS(), x + 5, y+(y_offset*y_spacing));				
				y_offset++;
			}
			if(showSpritesTreeSize == true){
				g.drawString("Number of Sprites in spriteTree: " + this.spriteTreeSize, x + 5, y+(y_offset*y_spacing));
				y_offset++;
			}
			if(showDrawTreeSize == true){
				g.drawString("Number of Sprites in drawTree: " + this.drawTreeSize, x + 5, y+(y_offset*y_spacing));
				y_offset++;			
			}
			if(showNumCollisionsFound == true){
				g.drawString("Number of Collisions Found: " + this.numCollisionsFound, x + 5, y+(y_offset*y_spacing));
				y_offset++;					
			}
			if(showNumCollisionsFound == true){
				g.drawString("Number of Collisions Found: " + this.numCollisionsFound, x + 5, y+(y_offset*y_spacing));
				y_offset++;				
				
			}
			if(showNumDoesCollideAttempts == true){
				g.drawString("Number of doesCollide checks performed: " + this.numDoesCollideAttempts, x+ 5, y+(y_offset*y_spacing));
				y_offset++;					
			}
			if(showNumAnimationCycles == true){
				g.drawString("Number of animation cycles completed: " + this.numAnimationCycles, x+ 5, y+(y_offset*y_spacing));
				y_offset++;					
			}
			
		}
	}
	
	public void incrementAnimationCycle(){		
		AnimationCycleUnit a = new AnimationCycleUnit(this.numAnimationCycles,
													  this.spriteTreeSize,
													  this.drawTreeSize,
													  this.numDoesCollideAttempts,
													  this.numCollisionsFound,
													  this.getFPS(),
													  this.animation_taskTimeNano,
													  this.draw_taskTimeNano,
													  this.think_taskTimeNano
													);
		log.add(a);
		resetCycleCounters();
		this.numAnimationCycles++;
	}
	
	
	private void resetCycleCounters(){
		
		numDoesCollideAttempts = 0;
		numCollisionsFound = 0;
		spriteTreeSize = 0;
		drawTreeSize = 0;
		
		
	}
	
	private class AnimationCycleUnit{
		int animationCycleId;
		int spriteTreeSize;
		int drawTreeSize;
		int numDoesCollidePerformed;
		int numCollisionsFound;
		double fPS;
		long cycleRunTotalTime;
		long cycleDrawTime;
		long cycleThinkTime;
		
		@SuppressWarnings("unused")
		public AnimationCycleUnit(int acid,
								  int sts, 
								  int dts, 
								  int ndcp, 
								  int ncf,
								  double fps,
								  long crtt,
								  long cdt,
								  long ctt
								  ){
			
			this.animationCycleId = acid;
			this.spriteTreeSize = sts;
			this.drawTreeSize = dts;
			this.numDoesCollidePerformed = ndcp;
			this.numCollisionsFound = ncf;
			this.fPS = fps;
			this.cycleRunTotalTime = crtt;
			this.cycleDrawTime = cdt;
			this.cycleThinkTime = ctt;
		}						
	}
		

	private class ProfileUnit{
		public int num;
		public long time;		
		
		public ProfileUnit( long time){
			
			this.time = time;			
		}
		
	}
	
}
